package apii.servlet.stud;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;

import apii.server.AlgoRunManager;
import apii.server.ServerThings;
import apii.servlet.ServletUtils;
import apii.servlet.UserRequesterData;

/**
 * 
 * @author evgeniy
 * 
 */
public class ZipUploadServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	public static final String BUNDLE_ID = "bundle_id";

	public static final int NO_BUNDLE = -1;

	/**
	 * @see HttpServlet#HttpServlet()
	 */
	public ZipUploadServlet() {
		super();
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		// TODO:
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		try {
			UserRequesterData urd = ServletUtils.extractRequesterData(request
					.getSession());
			if ((urd.sessionId == null) || !urd.logged
					|| (urd.role != UserRequesterData.ROLE_STUD)) {
				ServletUtils.redirectToUrl(response, "/login");
			} else if (urd.userId == UserRequesterData.UNKNOWN_ID) {
				throw new Error();
			} else {
				String bundleIdString;
				if ((bundleIdString = (String) request.getAttribute(BUNDLE_ID)) != null) {
					System.out.println("BUNDLE_ID DETECTED");
					int bundleId = Integer.parseInt(bundleIdString);
					int labId = (Integer) request
							.getAttribute(StudRootServlet.LAB_ID);
					int discId = (Integer) request
							.getAttribute(StudRootServlet.DISCIPLINE_ID);
					boolean result = commitBundle(bundleId, labId, urd);
					if (result) {
						response.sendRedirect(ServletUtils.PREFIX
								+ "/stud/all_disciplines/"
								+ ServletUtils.getDisciplineURLElement(discId)
								+ "/" + ServletUtils.getLabURLElement(labId));
					} else {

					}
				} else {
					System.out.println("BUNDLE_ID NOT DETECTED");
					boolean isMultipart = ServletFileUpload
							.isMultipartContent(request);
					int labId = (Integer) request
							.getAttribute(StudRootServlet.LAB_ID);
					int discId = (Integer) request
							.getAttribute(StudRootServlet.DISCIPLINE_ID);
					if (labId > 0) {
						if (isMultipart) {
							DiskFileItemFactory factory = new DiskFileItemFactory();
							factory.setSizeThreshold(1024 * 1024);
							ServletFileUpload upload = new ServletFileUpload(
									factory);
							upload.setSizeMax(1024 * 1024 * 10);

							try {
								List<FileItem> items = upload
										.parseRequest(request);
								Iterator<FileItem> i = items.iterator();
								if (i.hasNext()) {
									FileItem item = (FileItem) i.next();
									if (!item.isFormField()) {
										int bundleId = addIntoDB(item,
												urd.userId);

										if (bundleId != NO_BUNDLE) {
											showBundleDiff(request, response,
													bundleId, labId, discId,
													urd);
										} else {
											request.getRequestDispatcher(
													"/illegal").forward(
													request, response);
										}
									} else {
										request.getRequestDispatcher("/illegal")
												.forward(request, response);
									}
								} else {
									request.getRequestDispatcher("/illegal")
											.forward(request, response);
								}
							} catch (Exception ex) {
								ex.printStackTrace();
							}
						} else {
							request.getRequestDispatcher("/illegal").forward(
									request, response);
						}
					} else {
						request.getRequestDispatcher("/illegal").forward(
								request, response);
					}
				}
			}
		} catch (SQLException ex) {
			ServletUtils.redirectToErrorPage(response, ex.getMessage());
			ex.printStackTrace();
		}
	}

	private void showBundleDiff(HttpServletRequest request,
			HttpServletResponse response, int bundleId, int labId, int discId,
			UserRequesterData urd) throws ServletException, SQLException,
			IOException {

		String zipUploadHandlerName = ServletUtils.PREFIX + "/stud/"
				+ StudRootServlet.ALL_DISCIPLINES + "/"
				+ ServletUtils.getDisciplineURLElement(discId) + "/"
				+ ServletUtils.getLabURLElement(labId) + "/"
				+ StudRootServlet.UPLOAD_ZIP;

		Connection c = ServerThings.getThings().getConnection();
		// TODO: пока что показывается просто список файлов, но потом надо
		// реализовать показ
		// diff-а по файлам
		PreparedStatement s = c
				.prepareStatement("SELECT apii.upload_codes.name, length(apii.upload_codes.code) "
						+ "FROM apii.upload_codes "
						+ "WHERE apii.upload_codes.upload_code_bundle_id = ?");
		s.setInt(1, bundleId);
		ResultSet codesToPostRS = s.executeQuery();

		PrintWriter pw = response.getWriter();
		ServletUtils.startCommonStudDoc(pw, urd, "Загрузка кода");
		pw.println("<table border =\"1\">");
		pw.println("<tr>");
		pw.println("<td><b>Файлы, которые Вы загружаете</b></td>");
		pw.println("<td><b>Размер</b></td>");
		pw.println("</tr>");
		while (codesToPostRS.next()) {
			pw.println("<tr>");
			pw.println("<td>" + codesToPostRS.getString(1) + "</td>");
			pw.println("<td>" + sizeToString(codesToPostRS.getInt(2)) + "</td>");
			pw.println("</tr>");
		}
		pw.println("</table>");
		ServletUtils.closeCommonStudDoc(pw, urd);
		c.close();
		pw.println("<form action=\"" + zipUploadHandlerName + "?" + BUNDLE_ID
				+ "=" + bundleId + "\" method=\"post\" "
				+ "enctype=\"multipart/form-data\">");
		pw.println("<input type=\"submit\" value=\"Подтвердить\"><br>");
		pw.println("</form>");
	}

	private int addIntoDB(FileItem item, int userId) throws Exception {
		InputStream in = item.getInputStream();
		int bundleId = NO_BUNDLE;

		try (Connection c = ServerThings.getThings().getConnection();) {

			ZipInputStream zin = new ZipInputStream(in);
			ZipEntry zEntry;
			boolean bundleEntryCreated = false;

			while ((zEntry = zin.getNextEntry()) != null) {

				// extract code
				ByteArrayOutputStream bout = new ByteArrayOutputStream();
				write(zin, bout);
				String code = new String(bout.toByteArray());

				// put temporarily the code to the db:
				if (!bundleEntryCreated) {
					bundleEntryCreated = true;
					bundleId = createBundleEntry(c, userId);
				}

				PreparedStatement s = c
						.prepareStatement("INSERT INTO apii.upload_codes VALUES (?, ?, ?);");
				s.setString(1, code);
				s.setString(2, zEntry.getName());
				s.setInt(3, bundleId);
				s.executeUpdate();

				// System.out.println("executing update ..." + s.toString());
				// System.out
				// .println("archive size: " + bout.toByteArray().length);
			}
			c.commit();
			in.close();
		} catch (IOException | SQLException ex) {
			System.out.println("ne zip file :)");
			ex.printStackTrace();
		}

		return bundleId;
	}

	private static final boolean commitBundle(int bundleId, int labId,
			UserRequesterData urd) throws SQLException {
		boolean result;
		try (Connection c = ServerThings.getThings().getConnection()) {
			// check that the is the correct id
			PreparedStatement checkStatement = c
					.prepareStatement("SELECT apii.upload_code_bundles.user_id "
							+ "FROM apii.upload_code_bundles "
							+ "WHERE apii.upload_code_bundles.upload_code_bundle_id = ?");
			checkStatement.setInt(1, bundleId);
			ResultSet checkRS = checkStatement.executeQuery();
			if (checkRS.next()) {
				int userId = checkRS.getInt(1);
				if (userId != urd.userId) {
					result = false;
				} else {
					PreparedStatement commitStatement = c
							.prepareStatement("INSERT INTO codes VALUES "
									+ "(?, " + "?, ?, ?, ?, ?, "
									+ "'2011-11-15 15:05:35', false);");
					commitStatement.setInt(2, labId);
					commitStatement.setInt(3, userId);

					PreparedStatement codesFromBundleStatement = c
							.prepareStatement("SELECT apii.upload_codes.name, "
									+ "apii.upload_codes.code "
									+ "FROM apii.upload_codes "
									+ "WHERE apii.upload_codes.upload_code_bundle_id = ?");
					codesFromBundleStatement.setInt(1, bundleId);
					ResultSet codesFromBundleRS = codesFromBundleStatement
							.executeQuery();

					Statement revisionStatement = c.createStatement();
					// lock db
					ResultSet codeLockRS = revisionStatement
							.executeQuery("SELECT apii.codes.revision FROM apii.codes "
									+ "WHERE apii.codes.lab_id = '"
									+ labId
									+ "' AND apii.codes.user_id = '"
									+ userId
									+ "' FOR UPDATE");
					int revision = 1;
					if (codeLockRS.next()) {
						Statement s = c.createStatement();
						ResultSet maxRevisionRS = s
								.executeQuery("SELECT max(apii.codes.revision) FROM apii.codes "
										+ "WHERE apii.codes.lab_id = '"
										+ labId
										+ "' AND apii.codes.user_id = '"
										+ userId + "'");
						maxRevisionRS.next();
						revision = maxRevisionRS.getInt(1) + 1;
					}
					commitStatement.setInt(4, revision);
					PreparedStatement codeIdStatement = c
							.prepareStatement("SELECT nextval('codes')");
					List<Integer> codeIds = new ArrayList<Integer>();
					while (codesFromBundleRS.next()) {
						ResultSet codeIdRS = codeIdStatement.executeQuery();
						codeIdRS.next();
						int codeId = codeIdRS.getInt(1);
						codeIds.add(codeId);
						commitStatement.setInt(1, codeId);
						commitStatement.setString(5,
								codesFromBundleRS.getString(1));
						commitStatement.setString(6,
								codesFromBundleRS.getString(2));
						commitStatement.executeUpdate();
					}
					// TODO: temporary! remove this later
					AlgoRunManager.getManager();
					c.commit();
					c.close();
					for (Integer codeId : codeIds) {
						AlgoRunManager.getManager().runCodeLabour(codeId);
					}
					result = true;
				}
			} else {
				result = false;
			}
			checkStatement.close();
		}

		return result;
	}

	private static final String sizeToString(int size) {
		double sizeInKb = size / (1024.0);
		return String.format("%.1fKB", sizeInKb);
	}

	private static final int createBundleEntry(Connection c, int userId)
			throws SQLException {
		// TODO: make prepared statement
		// TODO: make good timestamp
		Statement getIdStatement = c.createStatement();
		ResultSet nextvalRS = getIdStatement
				.executeQuery("SELECT nextval('apii.upload_code_bundles')");
		if (!nextvalRS.next()) {
			throw new Error();
		}
		int bundleId = nextvalRS.getInt(1);
		PreparedStatement insertStatement = c
				.prepareStatement("INSERT INTO upload_code_bundles VALUES (?, ?)");
		insertStatement.setInt(1, bundleId);
		insertStatement.setInt(2, userId);
		insertStatement.executeUpdate();

		return bundleId;
	}

	private static final void write(InputStream in, OutputStream out)
			throws IOException {
		byte[] buffer = new byte[1000];
		int length;
		while ((length = in.read(buffer)) >= 0) {
			out.write(buffer, 0, length);
		}
	}
}
